#include "param.h"
#include "types.h"
#include "uthread.h"
#include "user.h"
#include "fcntl.h"

#define BUFF_SIZE 256
#define SALAD 0
#define PASTA 1
#define STEAK 2
#define NUMBEROFWAITERS 3
#define LONG_EATING_PROCESS 100000
#define SHORT_EATING_PROCESS 1000

int NUM_OF_THREADS, MAX_SEATS, MAX_SALAD, MAX_PASTA, MAX_STEAK;
int initialStudents, joiningStudents, finishedStudents, waiters, seats;
 
int maxFood[3];
int food[3];

int fd;
    
   
int
length(void* arr){
    return sizeof(arr)/sizeof(int);
}

int
readConfFile(int res[]){

    int fd, resIndex;
    char buff[BUFF_SIZE];
    char input[2] = {'0','\0'};
    
    fd = open("ass2_conf.txt", O_RDONLY);
    if(fd < 0)
		return -1;
    
    resIndex = 0;
    int position = 0;
    while (read(fd,input,1) != 0){
		if (strcmp(input,"\n") == 0){
			buff[position] = '\0';
			res[resIndex] = atoi(buff);
			resIndex++;
			position=0;
		}
		else{
		   buff[position] = input[0];
		   position++;
		}
    }
    buff[position] = '\0';
    res[resIndex] = atoi(buff);
    close(fd);
    return 0;
}

int
writeStatFile(int teTime, int trTime, int twTime){
   
	printf(fd, "Simulation: User-Level-Threads\nTotal run time is: %d\nAverage run time per user-level-thread is: %d\nAverage wait time per user-level thread is: %d\n",teTime,trTime,twTime);
    close(fd);
    return 0;
}

void
student_take_sit(){
	// aquire a seat at the table
	--seats;
}

void
student_eat(int timeToEat){
	int i,j,k;
	int t;
	for(i=1;i<=timeToEat;i++){
		for(j=1;j<=1000;j++){
			t=1;
			for(k=1;k<=20;k++){
				t*=k;
			}
		}
		uthread_yield();
	}
}

void
student_take_food(){

	int studentId = uthread_self().tid;
	int cFood;
	// first take food number (studentId)%3
	// then take food number (studentId+1)%3
	
	int foodTaken = 0;
	while (foodTaken < 2){
		cFood = (studentId)%3;
		if (food[cFood]>0){
			--food[cFood];
			++foodTaken;
			printf(fd,"Student %d acquired %d\n",studentId,cFood);
			printf(2,"Student %d acquired %d\n",studentId,cFood);

		}
		cFood = (studentId+1)%3;
		if ((foodTaken==1) && (food[cFood]>0)){
			--food[cFood];
			++foodTaken;
			printf(fd,"Student %d acquired %d\n",studentId,cFood);
			printf(2,"Student %d acquired %d\n",studentId,cFood);
		}
		if (foodTaken==2){
			printf(fd,"Student %d started long eating process\n",studentId);
			printf(2,"Student %d started long eating process\n",studentId);

			student_eat(LONG_EATING_PROCESS);
		}
		else{
			printf(fd,"Student %d waits for %d\n",studentId,cFood);
			printf(2,"Student %d waits for %d\n",studentId,cFood);
			uthread_yield();
		}
	}
	uthread_yield();
	
	while (foodTaken<3){
		cFood = (studentId+2)%3;
		// last take food number (studentId+2)%3
		if (food[cFood]>0){
			--food[cFood];
			++foodTaken;
			printf(fd,"Student %d acquired %d\n",studentId,cFood);
			printf(2,"Student %d acquired %d\n",studentId,cFood);
		}
		if (foodTaken==3){
			printf(fd,"Student %d started short eating process\n",studentId);
			printf(2,"Student %d started short eating process\n",studentId);
			student_eat(SHORT_EATING_PROCESS);
		}
		else{
			printf(fd,"Student %d waits for %d\n",studentId,cFood);
			printf(2,"Student %d waits for %d\n",studentId,cFood);
			// notify the waiter that we finished eating	
			uthread_yield();
		}
	}
	uthread_yield();
}

void
student_leave_table(){
	// release the seat. notify the hostess
	printf(fd,"Student %d left the table\n",uthread_self().tid);
	printf(2,"Student %d left the table\n",uthread_self().tid);
	--seats;
	--finishedStudents;
	// exit the thread
	uthread_exit();
}

void
student_life_cycle(){
	student_take_food();
	student_leave_table();
}

void
hostess_enter_students(){
	int index;
	int tmp = 0;
	
	while (seats <= MAX_SEATS){
		// if seats are full - wait
		if (initialStudents==0){
			// if there are no more students to enter, exit
			if (initialStudents==0 && tmp==0){
				tmp=1;
				initialStudents = joiningStudents;
			}
			else if (initialStudents==0){
				break;
			}
			//uthread_setpr(1);
		}else if (seats == MAX_SEATS){
			uthread_setpr(1);
		}else{
			uthread_setpr(0);
			if (seats<MAX_SEATS && initialStudents>0){
				index = uthread_create(student_life_cycle,1);
				printf(fd,"Student %d joined the table\n",index);
				printf(2,"Student %d joined the table\n",index);
				--initialStudents;
				++seats;
				// a student takes a sit in student_take_sit function
			}
		}
		uthread_yield();
	}
	uthread_exit();
}

void
waiter_refill_food(){
	// cfaw = current food and waiter (minus 1 cause tid starts with 1)
	int cfaw = uthread_self().tid-1;
	
	while(food[cfaw]<=maxFood[cfaw]){
	
		if (finishedStudents==0){
			--waiters;	
			if (waiters==0){
				int totalElapsedTime, totalRunTime, totalWaitTime;
				retrieve_process_statistics(&totalElapsedTime,&totalRunTime,&totalWaitTime);
				writeStatFile(totalElapsedTime,(totalRunTime/NUM_OF_THREADS),(totalWaitTime/NUM_OF_THREADS));
			}
			uthread_exit();
		}
		
		// if waiter is refilling food - 1 refill
		if (food[cfaw]<maxFood[cfaw]){
			++food[cfaw];
			printf(fd,"Waiter %d increased his buffer to %d/%d\n",cfaw,food[cfaw],maxFood[cfaw]);
			printf(2,"Waiter %d increased his buffer to %d/%d\n",cfaw,food[cfaw],maxFood[cfaw]);
		}

		uthread_yield();
	}
}


int
main(int args, char** agrv){

    int wIndex;
    int res[6];

	fd = open("ass2_log.txt", O_CREATE | O_WRONLY);
	
	if (fd < 0){
		return -1;
	}
	
    // reading configuration file
    if (readConfFile(res) < 0){
		return -1;
    }
    
    initialStudents = res[0];
    joiningStudents = res[1];
	finishedStudents = initialStudents+joiningStudents;
    MAX_SEATS = res[2];
    MAX_SALAD = res[3];
    MAX_PASTA = res[4];
    MAX_STEAK = res[5];
	seats = 0;  // number of taken seats
    waiters = NUMBEROFWAITERS;
	
	NUM_OF_THREADS = finishedStudents+4;
	
    food[0] = MAX_SALAD;
    food[1] = MAX_PASTA;
    food[2] = MAX_STEAK;
    maxFood[0] = MAX_SALAD;
    maxFood[1] = MAX_PASTA;
    maxFood[2] = MAX_STEAK;
    
    // creating threads for waiters
    for (wIndex = 0; wIndex < NUMBEROFWAITERS; wIndex++){
		uthread_create(waiter_refill_food,1);
    }
    
	// creating thread for hostess
    uthread_create(hostess_enter_students,0);
    
    uthread_start_all();
	
    exit();
}
